package com.yuan.yblog.search.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yuan.yblog.crud.entity.Category;
import com.yuan.yblog.crud.entity.Post;
import com.yuan.yblog.crud.entity.User;
import com.yuan.yblog.crud.service.ICategoryService;
import com.yuan.yblog.crud.service.IPostService;
import com.yuan.yblog.crud.service.IUserService;
import com.yuan.yblog.search.pojo.EsPostDTO;
import com.yuan.yblog.search.repository.EsPostRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Slf4j
public class EsPostService {

    @Autowired
    EsPostRepository esPostRepository;

    @Autowired
    ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    IPostService iPostService;

    @Autowired
    IUserService iUserService;

    @Autowired
    ICategoryService iCategoryService;

    /**
     * 根据关键字进行精确/模糊搜索
     * @param keyword 搜索关键字
     * @param field 搜索字段
     * @param isPrecise 是否精确搜索，默认false模糊搜索
     * @param pageNumber 页码
     * @param pageSize 页大小
     * @param sortKey 排序字段
     * @param isDesc 是否降序
     * @return 封装分页后的结果
     * @参考教程： <a href="https://blog.csdn.net/ZunXin_2580/article/details/131654629">ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用</a>
     */
    public IPage<EsPostDTO> search (String keyword, String field, boolean isPrecise, int pageNumber, int pageSize, String sortKey, Boolean isDesc) {
        NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
        if (isPrecise) {
            // 精确
            nativeQueryBuilder.withQuery(
                    queryBuilder -> queryBuilder.fuzzy(
                            termBuilder -> termBuilder
                                    .field(field)
                                    .value(keyword)
                    )
            );
            /* 上述lambda表达式等价于下面代码，详细参考：java-lambda-匿名函数相关规则
            nativeQueryBuilder.withQuery(new Function<Query.Builder, ObjectBuilder<Query>>() {
                @Override
                public ObjectBuilder<Query> apply(Query.Builder builder) {
                    return builder.term(new Function<TermQuery.Builder, ObjectBuilder<TermQuery>>() {
                        @Override
                        public ObjectBuilder<TermQuery> apply(TermQuery.Builder builder) {
                            builder.field(field).value(keyword);
                            return builder;
                        }
                    });
                }
            });
            */
        } else {
            // 模糊
            nativeQueryBuilder.withQuery(
                    queryBuilder -> queryBuilder.wildcard(
                            wildcardBuilder -> wildcardBuilder
                                    .field(field)
                                    .value("*"+keyword+"*")
                    )
            );
            /* 上述lambda表达式等价于下面代码，详细参考：java-lambda-匿名函数相关规则
            nativeQueryBuilder.withQuery(new Function<Query.Builder, ObjectBuilder<Query>>() {
                @Override
                public ObjectBuilder<Query> apply(Query.Builder builder) {
                    return builder.wildcard(new Function<WildcardQuery.Builder, ObjectBuilder<WildcardQuery>>() {
                        @Override
                        public ObjectBuilder<WildcardQuery> apply(WildcardQuery.Builder builder) {
                            builder.field(field).value("*"+keyword+"*");
                            return builder;
                        }
                    });
                }
            });*/
        }
        // 构造条件
        NativeQuery nativeQuery = nativeQueryBuilder.build();

        // 统计总数
        long total = elasticsearchTemplate.count(nativeQuery, EsPostDTO.class);

        // 分页条件
        PageRequest page = PageRequest.of(pageNumber, pageSize, isDesc? Sort.Direction.DESC : Sort.Direction.ASC, sortKey);
        nativeQuery.setPageable(page);

        // 查询
        List<SearchHit<EsPostDTO>> searchHits = elasticsearchTemplate.search(nativeQuery, EsPostDTO.class).getSearchHits();

        // 打印查询的DSL
        log.debug(Objects.requireNonNull(nativeQuery.getQuery()).toString());

        // 封装
        IPage<EsPostDTO> result = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNumber, pageSize, total);
        result.setRecords(searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()));

        // 返回结果
        return result;
    }

    /**
     * 根据id插入
     * @param id 文章id
     * @return true表示操作成功
     */
    public boolean insert(Long id) {
        if (id == null || exist(id)) {
            // 已存在则不插入
            return false;
        }
        Post post = iPostService.selectByIdAndBuildCache(id);
        User user = null;
        Category category = null;
        if (post != null && post.getUserId() != null) {
            user = iUserService.selectByIdAndBuildCache(post.getUserId());
        }
        if (post != null && post.getCategoryId() != null) {
            category = iCategoryService.selectByIdAndBuildCache(post.getCategoryId());
        }
        EsPostDTO esPostDTO = EsPostDTO.getEsPostDTO(post, user, category);
        esPostRepository.save(esPostDTO);
        return true;
    }

    /**
     * 根据id删除
     * @param id 文章id
     * @return true表示操作成功
     */
    public boolean delete(Long id) {
        if (id == null) {
            return false;
        }
        if (exist(id)) {
            esPostRepository.deleteById(id);
            return true;
        }
        return false;
    }

    /**
     * 根据id更新
     * @param id 文章id
     * @return true表示操作成功
     */
    public boolean update(Long id) {
        if (id == null) {
            return false;
        }
        if (delete(id)) {
            return insert(id);
        }
        return false;
    }

    /**
     * 判断文章是否存在
     * @param id 文章id
     * @return true表示存在
     */
    public boolean exist (Long id) {
        return esPostRepository.existsById(id);
    }


    public void initImport() {
        // 当前页码
        long pageNumber = 0;
        // 批操作数目
        long pageSize = 10000;

        while (true) {
            int succeedCount = 0;
            // 获取此页
            List<Post> posts = iPostService.getBaseMapper().selectList(new QueryWrapper<Post>()
                    .select("id")
                    .last("limit " + pageNumber * pageSize + "," + pageSize)
            );
            if(posts == null || posts.isEmpty()) {
                break;
            }

            // 处理
            for (Post post : posts) {
                if (post !=null && post.getId() != null ) {
                    // 插入数据
                    if (insert(post.getId())) {
                        // 统计成功数目
                        succeedCount++;

                    }
                }
            }
            log.info("mysql第{}次导入ES，计划导入数目: {}，实际导入数目: {}", pageNumber+1, pageSize, succeedCount);
            pageNumber++;
        }
        log.info("mysql导入ES全部完成！");
    }

}
